using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Drawing;
using RideMe.Core;
using RideMe.Core.Properties;
using RideMe.Presentation.Properties;
using System.Diagnostics;


namespace RideMe.Presentation.Controls
{
    internal static class TreeViewBuilder
    {

        public static event EventHandler ProjectItemFound;

        private static void OnProjectItemFound(EventArgs e)
        {
            if (ProjectItemFound != null)
            {
                ProjectItemFound(null, e);
            }
        }

        private static Dictionary<string, int> _iconHash = new Dictionary<string, int>();
        private static ImageList _images;

        private const int DefaultIconIndex = 3;

        private static bool _hideExcludedFolders = true;

        /// <summary>
        /// Gets or sets whether excluded folderes show be displayed.
        /// </summary>
        public static bool HideExcludedFolders
        {
            get { return _hideExcludedFolders; }
            set { _hideExcludedFolders = value; }
        }

        private static string _projectPath;

        public static string ProjectPath
        {
            get { return _projectPath; }
            set { _projectPath = value; }
        }
	

        /// <summary>
        /// Populates the tree.
        /// </summary>
        /// <param name="tree">The tree.</param>
        /// <param name="projectPath">The project path.</param>
        /// <param name="imageList">The image list.</param>
        public static void PopulateTree(TreeView tree, ImageList imageList)
        {
            Debug.Assert(_projectPath.Trim().Length > 0);

            _images = imageList;

            ResetTree(tree);
            AddRootNode(tree, _projectPath);
            AddSubFolders(_projectPath, tree.Nodes[0]);
            AddFiles(_projectPath, tree.Nodes[0]);
            tree.Nodes[0].Expand();
        }

        /// <summary>
        /// Resets the tree.
        /// </summary>
        /// <param name="tree">The tree.</param>
        public static void ResetTree(TreeView tree)
        {
            if (tree.InvokeRequired)
            {
                tree.Invoke(new MethodInvoker(
                    delegate()
                    {
                        ResetTree(tree);
                    }
                    ));
            }
            else
            {
                tree.Nodes.Clear();
            }
        }

        /// <summary>
        /// Adds the root node.
        /// </summary>
        /// <param name="tree">The tree.</param>
        /// <param name="projectPath">The project path.</param>
        private static void AddRootNode(TreeView tree, string projectPath)
        {
            TreeNode parentNode = new TreeNode(projectPath, 0, 0);
            parentNode.Tag = projectPath;
            tree.Nodes.Add(parentNode);
        }

        /// <summary>
        /// Adds the node.
        /// </summary>
        /// <param name="parent">The parent.</param>
        /// <param name="nodeText">The node text.</param>
        /// <param name="imageIndex">Index of the image.</param>
        /// <param name="fullPath">The full path.</param>
        /// <returns></returns>
        public static TreeNode AddNode(TreeNode parent, string nodeText, int imageIndex, string fullPath)
        {
            TreeNode subNode = new TreeNode(nodeText, imageIndex, imageIndex);
            subNode.Tag = fullPath;
            parent.Nodes.Add(subNode);
            return subNode;
        }

        /// <summary>
        /// Adds the sub folders.
        /// </summary>
        /// <param name="path">The path.</param>
        /// <param name="parent">The parent.</param>
        public static void AddSubFolders(string path, TreeNode parent)
        {
            // By default, dirs are returned by creation date. Sort by name.
            string[] directories = Directory.GetDirectories(path);
            Array.Sort(directories, StringComparer.Create(System.Globalization.CultureInfo.InvariantCulture, true));

            foreach (string dir in directories)
            {
                DirectoryInfo di = new DirectoryInfo(dir);

                // If this is an excluded folder, and the user
                // show all files isn't enabled then don't show it.
                if (ShowThisFolder(di.FullName))
                {
                    TreeNode child = AddNode(parent, di.Name, 1, di.FullName);
                    
                    // If this folder is directly under the root of the app
                    // signal a found item so the progress bar can update.
                    if (di.Parent.FullName == _projectPath)
                    {
                        OnProjectItemFound(EventArgs.Empty);
                    }
                    AddSubFolders(di.FullName, child);
                    AddFiles(di.FullName, child);
                }
            }
        }

        /// <summary>
        /// Returns whether the specified folder should be shown.
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        private static bool ShowThisFolder(string path)
        {
            // We're not hiding anything so, just show it.
            if (!_hideExcludedFolders) return true;

            string[] excludedFolders = Resources.FolderExclusionList.Split(',');

            bool show = true;

            foreach (string exludedFolderName in excludedFolders)
            {
                if (path.Contains(exludedFolderName))
                {
                    show = false;
                    break;
                }
            }

            return show;
        }

        /// <summary>
        /// Adds the files.
        /// </summary>
        /// <param name="directory">The directory.</param>
        /// <param name="folderNode">The folder node.</param>
        public static void AddFiles(string directory, TreeNode folderNode)
        {
            DirectoryInfo di = new DirectoryInfo(directory);
            foreach (FileInfo fi in di.GetFiles())
            {
                AddNode(folderNode, fi.Name, FindIcon(fi), fi.FullName);
                OnProjectItemFound(EventArgs.Empty);
            }
        }

        /// <summary>
        /// Finds the icon.
        /// </summary>
        /// <param name="fi">The fi.</param>
        /// <returns></returns>
        public static int FindIcon(FileInfo fi)
        {
            int imageIndex = 0;

            // We only want to add icons to the hash
            // that haven't been added yet. My kung fu is strong, yo.
            // Because I said so!
            if (!_iconHash.ContainsKey(fi.Extension))
            {
                try
                {
                    Image icon = IconExtractor.GetIcon(fi.FullName, false);
                    _images.Images.Add(icon);
                    imageIndex = _images.Images.Count - 1;
                    _iconHash.Add(fi.Extension, imageIndex);
                }
                catch (ArgumentException)
                {
                    // This has only happend a couple times
                    // but we have to catch it. So, if we
                    // can't get the icon. Assign a default.
                    imageIndex = DefaultIconIndex;
                }
            }
            else
            {
                imageIndex = _iconHash[fi.Extension];
            }

            return imageIndex;
        }

        /// <summary>
        /// Adds the found file.
        /// </summary>
        /// <param name="fullPath">The full path.</param>
        /// <param name="name">The name.</param>
        /// <param name="tree">The tree.</param>
        public static void AddFoundFile(string fullPath, string name, TreeView tree)
        {
            if (tree.Nodes.Count == 0) return;

            _matchedNode = null;

            TreeNode nodeToAddTo = tree.Nodes[0];

            // If the file was found in the root of the project
            // we don't need to search.
            if (fullPath != Path.Combine(ProjectService.ProjectPath, name))
            {
                nodeToAddTo = FindTreeNode(Path.GetDirectoryName(fullPath), tree, tree.Nodes[0]);
            }

            if (nodeToAddTo == null) return;

            // This is a check to see if this is a directory.
            if (Directory.Exists(fullPath))
            {
                TreeNode node = AddNode(nodeToAddTo, Path.GetFileName(name), 1, fullPath);
                AddSubFolders(fullPath, node);
            }
            else
            {
                // Nope it's a file.
                int imageIndex = FindIcon(new FileInfo(fullPath));
                AddNode(nodeToAddTo, Path.GetFileName(name), imageIndex, fullPath);
            }
        }

        /// <summary>
        /// Deletes the node by path.
        /// </summary>
        /// <param name="fullPath">The full path.</param>
        /// <param name="tree">The tree.</param>
        public static void DeleteNodeByPath(string fullPath, TreeView tree)
        {
            if (tree.Nodes.Count == 0) return;

            _matchedNode = null;

            TreeNode nodeToDelete = FindTreeNode(fullPath, tree, tree.Nodes[0]);

            if (nodeToDelete != null)
            {
                tree.Nodes.Remove(nodeToDelete);
            }
        }

        private static TreeNode _matchedNode;

        /// <summary>
        /// Gets or sets the matched node.
        /// </summary>
        public static TreeNode MatchedNode
        {
            get { return _matchedNode; }
            set { _matchedNode = value; }
        }

        /// <summary>
        /// Searches all the ndes for a specific path
        /// </summary>
        /// <param name="path">The path to search for.</param>
        /// <param name="tree">The tree.</param>
        /// <param name="searchNode">The search node.</param>
        /// <returns></returns>
        public static TreeNode FindTreeNode(string path, TreeView tree, TreeNode searchNode)
        {           
            foreach (TreeNode node in searchNode.Nodes)
            {
                if (IsMatch(node, path))
                {
                    _matchedNode = node;
                }

                if (node.Nodes.Count > 0)
                {
                    if (_matchedNode == null)
                    {
                        FindTreeNode(path, tree, node);
                    }
                    else
                    {
                        return _matchedNode;
                    }
                }
            }

            return _matchedNode;
        }

        /// <summary>
        /// Determines whether the specified node matches the search.
        /// </summary>
        /// <param name="node">The node.</param>
        /// <param name="searchPath">The search path.</param>
        /// <returns>
        /// 	<c>true</c> if the specified node is match; otherwise, <c>false</c>.
        /// </returns>
        private static bool IsMatch(TreeNode node, string searchPath)
        {

            bool isMatch = false;
            if (node.Tag != null)
            {
                isMatch = node.Tag.ToString() == searchPath;
            }

            return isMatch;
        }

        /// <summary>
        /// Refreshes the node files.
        /// </summary>
        /// <param name="path">The path.</param>
        /// <param name="node">The node.</param>
        public static void RefreshNodeFiles(string path, TreeNode node)
        {
            node.Nodes.Clear();

            if (Directory.GetDirectories(path).Length > 0)
            {
                AddSubFolders(path, node);
            }
            else
            {
                AddFiles(path, node);
            }

        }
    }
}
